package dm.daemon;

import java.sql.SQLException;

import org.dom4j.DocumentException;

import dm.bsi.Container;
import dm.bsi.Log;
import dm.bsi.Node;
import dm.bsi.Service;
import indi.zjh.dp.db.Configuration;
import indi.zjh.dp.db.Session;
import indi.zjh.dp.error.CommandExecuteException;
import indi.zjh.dp.error.UnsuportedFieldException;
import indi.zjh.dp.orm.MethodFactory;
import indi.zjh.dp.util.FileUtil;
import indi.zjh.dp.util.SystemCmd;

public class NodeDaemon implements Runnable{
	
	private static final int STATE_INIT = 0; // 初始化
	private static final int STATE_PING = 1; // 准备ping服务器节点
	private static final int STATE_TEST_SSH = 2; // 检查服务器节点的ssh免密码登录
	private static final int STATE_TEST_DOCKER = 3; // 检查服务器节点是否安装docker
	private static final int STATE_TEST_NGINX = 4; // 检查服务器节点是否有nginx镜像
	private static final int STATE_COPY_NGINX = 5; // 拷贝nginx镜像, 并且导入
	private static final int STATE_INSTALL_NGINX = 6; // 安装导入nginx镜像
	private static final int STATE_TEST_LSB = 7; // 检查服务器节点是否安装lsb
	private static final int STATE_GET_OS_INFO = 8; // 获取服务器节点的操作系统信息
	private static final int STATE_TEST_DNS = 9; // 检查服务器节点是否有dns镜像
	private static final int STATE_COPY_DNS = 10; // 拷贝dns镜像, 并且导入
	private static final int STATE_INSTALL_DNS = 11; // 安装导入的dns镜像
	
	private static final int STATE_TEST_ALIVE = 100; // 测试服务器节点心跳
	private static final int STATE_GET_CPU_INFO = 101; // 获取服务器CPU使用率
	private static final int STATE_GET_MEM_INFO = 102; // 获取服务器内存使用率
	private static final int STATE_START_NGINX_CONTAINER = 103; // 测试服务器nginx容器状态
	private static final int STATE_START_DNS_CONTAINER = 104; // 测试服务器dns容器状态
	
	private static final int INIT_INTERVAL = 500; // 状态机初始化运行时间间隔, 单位:毫秒
	private static final int RUN_INTERVAL = 2000; // 状态机运行时间间隔
	
	protected boolean isReady = false; // 服务器节点是否准备就绪, 就绪后才可以创建容器
	protected int state = STATE_INIT; // 服务器节点状态机当前状态
	protected int prestate = 9999; // 服务器节点状态机前一个状态
	protected Node node = null; // 关联的服务器节点, 一个节点一个状态机
	protected int interval = INIT_INTERVAL; // 当前状态机运行时间间隔
	
	public NodeDaemon(Node node) {
		// TODO Auto-generated constructor stub
		this.node = node;
	}
	
	private void changeState(int newState)
	{
		this.prestate = this.state;
		this.state = newState;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		while(true)
		{
			Session session = null;
			String cmd = null;
			String response = null;
			try
			{
				session = Configuration.getSessionFactory().createSession();
				
				this.node = (Node)session.getById(Node.class, node.getId());
				
				//System.out.println("state = "+this.state+", prestate = "+this.prestate);
				switch (this.state) {
				case STATE_INIT:
					// 检查服务器节点ip
					
					if(this.prestate != this.state) Log.info(session, node, "检查服务器节点IP");
					
					if(node.getIp().equals("nil"))
					{
						if(this.prestate != this.state) Log.failure(session, this.node, "服务器节点IP未设置");	
						break;
					}
					this.changeState(STATE_PING);
					break;
				
				case STATE_PING:
					// ping服务器节点
					cmd = "ping -c 1 " + this.node.getIp();
					response = SystemCmd.execCMD(cmd, 5);
					if(this.prestate != this.state) Log.info(session, node, "ping服务器节点IP:"+cmd+":"+response);
					if(!response.contains("1 received, 0% packet loss"))
					{
						if(this.prestate != this.state) Log.failure(session, this.node, "无法ping通服务器节点");
						break;
					}
					this.changeState(STATE_TEST_SSH);
					break;
					
				case STATE_TEST_SSH:
					// 测试服务器节点免密码登录权限
					cmd = "ssh root@" + this.node.getIp() + " echo OK";
					response = SystemCmd.execCMD(cmd, 5);
					if(this.prestate != this.state) Log.info(session, node, "测试服务器节点免密码ssh登录权限:"+cmd+":"+response);
					if(!response.contains("OK"))
					{
						if(this.prestate != this.state) Log.failure(session, this.node, "服务器节点没有提供ssh免密码登录权限, 请设置");
						break;
					}
					this.changeState(STATE_TEST_DOCKER);
					break;
					
				case STATE_TEST_DOCKER:
					// 测试服务器节点是否安装docker
					cmd = "ssh root@" + this.node.getIp() + " rpm -qa|grep -c docker";
					response = SystemCmd.execCMD(cmd, 10).trim();
					if(this.prestate != this.state) Log.info(session, node, "测试服务器节点是否安装docker:"+cmd+":"+response);
					
					if(response.equals("0"))
					{
						cmd = "ssh root@" + this.node.getIp() + " yum install -y docker";
						response = SystemCmd.execCMD(cmd, 300);
						if(this.prestate != this.state) Log.info(session, node, "安装docker:"+cmd+":"+response);
						if(!response.contains("already installed") && !response.contains("Complete"))
						{
							if(this.prestate != this.state) Log.failure(session, this.node, "安装docker失败, 请手动在服务器节点安装docker");
							break;
						}
					}
					else 
					{
						if(this.prestate != this.state) Log.info(session, node, "服务器节点已经安装docker");
					}
					
					cmd = "ssh root@" + this.node.getIp() + " ifconfig docker0|grep broadcast|awk '{print $2}'";
					response = SystemCmd.execCMD(cmd, 10);
					node.setDockerGatewayIp(response.trim());
					Log.info(session, node, "保存服务器节点docker网关ip, "+response);
					
					this.changeState(STATE_TEST_NGINX);
					break;
					
				case STATE_TEST_NGINX:
					// 测试服务器节点是否有nginx镜像
					cmd = "ssh root@" + this.node.getIp() + " docker images | grep -c dmnginx";
					response = SystemCmd.execCMD(cmd, 5);
					if(this.prestate != this.state) Log.info(session, node, "测试服务器节点是否有nginx镜像:"+cmd+":"+response);
					// 没有dmnginx镜像
					if(response.equals("0"))
					{
						this.changeState(STATE_COPY_NGINX);
						if(this.prestate != this.state) Log.info(session, node, "测试服务器节点没有nginx镜像");
					}
					// 有dmnginx镜像
					else
					{
						this.changeState(STATE_TEST_LSB);
						if(this.prestate != this.state) Log.info(session, node, "测试服务器节点已经有nginx镜像");
					}
					break;
					
				case STATE_COPY_NGINX:
					// 拷贝nginx镜像到服务器节点
					cmd = "scp image/dmnginx.tgz root@" + node.getIp() + ":/tmp";
					response = SystemCmd.execCMD(cmd, 300);
					if(this.prestate != this.state) Log.info(session, node, "拷贝nginx镜像到测试服务器节点:"+cmd+":"+response);
					this.changeState(STATE_INSTALL_NGINX);
					break;
				
				case STATE_INSTALL_NGINX:
					// 导入nginx镜像到服务器节点docker管理
					cmd = "ssh root@" + node.getIp() + " 'docker load < /tmp/dmnginx.tgz'";
					response = SystemCmd.execCMD(cmd, 300);
					if(this.prestate != this.state) Log.info(session, node, "加载nginx镜像到测试服务器节点:"+cmd+":"+response);
					if(!response.contains("Loaded image: dmnginx:latest"))
					{
						if(this.prestate != this.state) Log.failure(session, this.node, "加载nginx镜像到测试服务器节点失败");
						break;
					}
					this.changeState(STATE_TEST_LSB);
					break;
					
				case STATE_TEST_LSB:
					// 在服务器节点安装lsb
					cmd = "ssh root@"+node.getIp()+" yum install -y lsb";
					response = SystemCmd.execCMD(cmd, 600);
					if(this.prestate != this.state) Log.info(session, node, "在服务器节点安装lsb:"+cmd+":"+response);
					if(!response.contains("already installed") && !response.contains("Complete"))
					{
						if(this.prestate != this.state) Log.failure(session, this.node, "在服务器节点安装lsb失败");
						break;
					}
					this.changeState(STATE_GET_OS_INFO);
					break;
					
				case STATE_GET_OS_INFO:
					// 获取服务器节点操作系统信息
					cmd = "ssh root@" + node.getIp() + " lsb_release -ri";
					response = SystemCmd.execCMD(cmd, 5);
					if(this.prestate != this.state) Log.info(session, node, "获取服务器节点操作系统信息:"+cmd+":"+response);
					
					String[] lines = response.split("\n");
					String[] osInfo = new String[lines.length];
					for(int i = 0; i < lines.length; ++i)
					{
						String[] vals = lines[i].split(":");
						if(vals.length > 1)
							osInfo[i] = vals[1].trim();
						else
							osInfo[i] = "未知";
					}
					String finalInfo = String.join(":", osInfo);
					Log.info(session, node, "获取操作系统信息:"+finalInfo);
					MethodFactory.BusinessIsModifiedMethod.invoke(this.node);
					this.node.setOsInfo(finalInfo);
					MethodFactory.BusinessIsModifiedMethod.invoke(this.node);
					
					this.changeState(STATE_TEST_DNS);
					break;
					
				case STATE_TEST_DNS:
					// 测试服务器节点是否有nginx镜像
					cmd = "ssh root@" + this.node.getIp() + " docker images | grep -c dmdns";
					response = SystemCmd.execCMD(cmd, 5);
					if(this.prestate != this.state) Log.info(session, node, "测试服务器节点是否有dns镜像:"+cmd+":"+response);
					// 没有dns镜像
					if(response.equals("0"))
					{
						this.changeState(STATE_COPY_DNS);
						this.interval = RUN_INTERVAL;
						if(this.prestate != this.state) Log.info(session, node, "测试服务器节点没有dns镜像");
					}
					// 有dmdns镜像
					else
					{
						this.changeState(STATE_TEST_ALIVE);
						if(this.prestate != this.state) Log.info(session, node, "测试服务器节点已经有dns镜像");
					}
					break;
					
				case STATE_COPY_DNS:
					// 拷贝nginx镜像到服务器节点
					cmd = "scp image/dmdns.tgz root@" + node.getIp() + ":/tmp";
					response = SystemCmd.execCMD(cmd, 300);
					if(this.prestate != this.state) Log.info(session, node, "拷贝dns镜像到测试服务器节点:"+cmd+":"+response);
					this.changeState(STATE_INSTALL_DNS);
					break;
					
				case STATE_INSTALL_DNS:
					// 导入dns镜像到服务器节点docker管理
					cmd = "ssh root@" + node.getIp() + " 'docker load < /tmp/dmdns.tgz'";
					response = SystemCmd.execCMD(cmd, 300);
					if(this.prestate != this.state) Log.info(session, node, "加载dns镜像到测试服务器节点:"+cmd+":"+response);
					
					if(response.contains("Loaded image ID:"))
					{
						String[] parts = response.split(":");
						String containerId = parts[2].trim();
						cmd = "ssh root@" + node.getIp() + " 'docker tag "+containerId+" dmdns'";
						response = SystemCmd.execCMD(cmd, 10);
						Log.info(session, node, cmd+":"+response);
					}
					else {
						if(!response.contains("Loaded image: dmdns:latest"))
						{
							if(this.prestate != this.state) Log.failure(session, this.node, "加载dns镜像到测试服务器节点失败");
							break;
						}
					}
					
					this.changeState(STATE_TEST_ALIVE);
					this.interval = RUN_INTERVAL;
					
					break;
					
				case STATE_TEST_ALIVE:
					// 服务器节点检测
					cmd = "ping -c 1 " + node.getIp();
					response = SystemCmd.execCMD(cmd, 5);
					if(!response.contains("1 received, 0% packet loss"))
					{
						this.node.setReady(false);
						if(this.prestate != this.state) Log.failure(session, node, "服务器节点检测超时");
					}
					else 
					{
						
						this.changeState(STATE_GET_CPU_INFO);
					}
					
					break;
					
				case STATE_GET_CPU_INFO:
					// 获取服务器节点CPU使用率
					cmd = "ssh root@" + node.getIp() + " 'cat /proc/stat | head -1'";
					response = SystemCmd.execCMD(cmd, 5).trim();
					
					String[] parts = response.split("\\s+");
					if(parts.length == 11)
					{
						long cpuUsedTime = Long.parseLong(parts[1]) + 
								Long.parseLong(parts[2]) + 
								Long.parseLong(parts[3]) +
								Long.parseLong(parts[5]) + 
								Long.parseLong(parts[6]) + 
								Long.parseLong(parts[7]);
						long cpuIdleTime = Long.parseLong(parts[4]);
						double didle = cpuIdleTime - node.getCpuIdleTime();
						double dused = cpuUsedTime - node.getCpuUsedRate();
						int usedRate = 0;
						if(didle <= dused)
							usedRate = (int)(1 - didle / dused) * 100;
						node.setCpuIdleTime(cpuIdleTime);
						node.setCpuUsedTime(cpuUsedTime);
						node.setCpuUsedRate(usedRate);
					}
					this.changeState(STATE_GET_MEM_INFO);
					break;
					
				case STATE_GET_MEM_INFO:
					// 获取服务器节点内存使用率
					cmd = "ssh root@"+node.getIp()+" 'cat /proc/meminfo | head -3'";
					response = SystemCmd.execCMD(cmd, 5);
					
					/*
					 MemTotal:         999696 kB
					 MemFree:          151616 kB
					 MemAvailable:     394280 kB
					 */
					lines = response.split("\n");
//					System.out.println("linenum="+lines.length);
					if(lines.length == 3)
					{
						String memTotal = lines[0].split(":")[1].trim();
						String memAvailable = lines[2].split(":")[1].trim();
						long memTotalVal = Long.parseLong(memTotal.split("\\s+")[0]);
						long memAvaiVal = Long.parseLong(memAvailable.split("\\s+")[0]);
						int memUsedRate = (int)(100 * (memTotalVal - memAvaiVal)/(double)memTotalVal);
						node.setMemInfo(memAvailable+" / "+memTotal);
						node.setMemUsedRate(memUsedRate);
					}
					this.changeState(STATE_START_NGINX_CONTAINER);
					break;
					
				case STATE_START_NGINX_CONTAINER:
					
					//docker ps --no-trunc --format='{{.ID}}#{{.Names}}#{{.Ports}}#{{.Status}}#{{.Image}}'
					cmd = "ssh root@"+node.getIp()+" docker ps -a --no-trunc --format='{{.ID}}#{{.Names}}#{{.Status}}' | grep dmnginxContainer";
					response = SystemCmd.execCMD(cmd, 10).trim();
					
					if(response.isEmpty())
					{
						//System.out.println("STATE_START_NGINX_CONTAINER docker run --name=dmnginxContainer -d -p 80:80 dmnginx");
						cmd = "ssh root@"+node.getIp()+" 'docker run --name=dmnginxContainer -d "+Service.getServicePortString()+" dmnginx'";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "启动服务器节点nginx:"+cmd+":"+response);
						
						// 拷贝nginx.conf文件
						cmd = "scp conf/nginx.conf root@"+node.getIp()+":/tmp/nginx.conf";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "拷贝nginx.conf到服务器节点:"+cmd+":"+response);
						
						cmd = "ssh root@"+node.getIp()+" 'docker cp /tmp/nginx.conf dmnginxContainer:/etc/nginx/'";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "拷贝nginx.conf到服务器节点nginx容器:"+cmd+":"+response);
						
						// 创建容器里的配置目录
						cmd = "ssh root@"+node.getIp()+" 'docker exec -i dmnginxContainer mkdir /etc/nginx/conf.d/tcp'";
						response = SystemCmd.execCMD(cmd, 10);
						Log.info(session, node, "服务器节点nginx容器创建tcp目录:"+cmd+":"+response);
						
						cmd = "ssh root@"+node.getIp()+" 'docker exec -i dmnginxContainer mkdir /etc/nginx/conf.d/http'";
						response = SystemCmd.execCMD(cmd, 10);
						Log.info(session, node, "服务器节点nginx容器创建http目录:"+cmd+":"+response);
						
					}
					else 
					{
						response = response.toLowerCase();
						if(response.contains("#up"))
						{
							String containerId = response.split("#")[0];
							cmd = "ssh root@"+node.getIp()+" docker ps -a --no-trunc --format='{{.ID}}' | grep "+containerId;
							response = SystemCmd.execCMD(cmd, 10);
							this.changeState(STATE_START_DNS_CONTAINER);
							//this.node.setReady(true);
						}
						else
						{
							String containerId = response.split("#")[0];
							cmd = "ssh root@"+node.getIp()+" 'docker rm -f "+containerId+"'";
							response = SystemCmd.execCMD(cmd, 30);
						}
					}
					
					break;
					
				case STATE_START_DNS_CONTAINER:
					
					//docker ps --no-trunc --format='{{.ID}}#{{.Names}}#{{.Ports}}#{{.Status}}#{{.Image}}'
					cmd = "ssh root@"+node.getIp()+" docker ps -a --no-trunc --format='{{.ID}}#{{.Names}}#{{.Status}}' | grep dmdnsContainer";
					response = SystemCmd.execCMD(cmd, 10).trim();
					
					if(response.isEmpty())
					{
						// docker run --name='bind' -d -p 53:53/udp -e WEBMIN_ENABLED=false -v /opt/scs/openxxs/bind:/data sameersbn/bind:latest
						cmd = "ssh root@"+node.getIp()+" 'docker run --name=dmdnsContainer -d -p 53:53/tcp -p 53:53/udp -e WEBMIN_ENABLED=false dmdns'";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "启动服务器节点dns:"+cmd+":"+response);
						
						// 拷贝named.conf.options文件
						cmd = "scp conf/named.conf.options root@"+node.getIp()+":/tmp/named.conf.options";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "拷贝named.conf.options到服务器节点:"+cmd+":"+response);
						
						cmd = "ssh root@"+node.getIp()+" 'docker cp /tmp/named.conf.options dmdnsContainer:/data/bind/etc/'";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "拷贝named.conf.options到服务器节点dns容器:"+cmd+":"+response);
						
						// 拷贝 named.conf.local.template
						String fileContent = FileUtil.getFileContent("conf/named.conf.local.template");
						//System.out.println("ip = "+node.getIp());
						String[] ipparts = node.getIp().split("\\.");
						//System.out.println("ipparts length = "+ipparts.length);
//						for(int kk = 0; kk < ipparts.length; ++kk)
//						{
//							System.out.println(kk+":"+ipparts[kk]);
//						}
						fileContent = fileContent.replace("@@HOST@@", ipparts[2]+"."+ipparts[1]+"."+ipparts[0]);
						String newfile = "conf/named.conf.local."+node.getIp();
						FileUtil.writeStringFile(newfile, fileContent);
						
						cmd = "scp "+newfile+" root@"+node.getIp()+":/tmp/named.conf.local";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "拷贝named.conf.local到服务器节点:"+cmd+":"+response);
						
						cmd = "ssh root@"+node.getIp()+" 'docker cp /tmp/named.conf.local dmdnsContainer:/data/bind/etc/'";
						response = SystemCmd.execCMD(cmd, 10).trim();
						Log.info(session, node, "拷贝named.conf.local到服务器节点dns容器:"+cmd+":"+response);
						

					}
					else 
					{
						response = response.toLowerCase();
						if(response.contains("#up"))
						{
							String containerId = response.split("#")[0];
							cmd = "ssh root@"+node.getIp()+" docker ps -a --no-trunc --format='{{.ID}}' | grep "+containerId;
							response = SystemCmd.execCMD(cmd, 10);
							this.node.setReady(true);
							this.changeState(STATE_TEST_ALIVE);
						}
						else
						{
							String containerId = response.split("#")[0];
							cmd = "ssh root@"+node.getIp()+" 'docker rm -f "+containerId+"'";
							response = SystemCmd.execCMD(cmd, 30);
						}
					}
					
					break;
					
				default:
					break;
				}
				
				session.save(this.node).commit();
			}
			catch(CommandExecuteException e)
			{
				try {
					if(session != null)
					{
						if(this.prestate != this.state) Log.failure(session, node, "运行cmd失败:"+e.getMessage());
						session.commit();
						Log.info(session, node, "该节点异常:"+e.getMessage());
						
						if(e.getMessage().contains("No route to host") || e.getMessage().contains("Connection refused"))
						{
							if(this.prestate != this.state) Log.failure(session, node, "当前节点已经失去联系, "+node.getIp());
							// 把node运行的容器都标记为unknown
							long[] containerIds = node.getContainers().getIds();
							for(int k = 0; k < containerIds.length; ++k)
							{
								Container lostContainer = (Container)session.getById(Container.class, containerIds[k]);
								lostContainer.setState(Container.UNKNOWN);
								session.save(lostContainer);
							}
							node.setReady(false);
							session.save(node);
							session.commit();
						}
						this.prestate = this.state;
					}
				} catch (ClassNotFoundException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				} catch (UnsuportedFieldException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				} catch (DocumentException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			catch(Exception e)
			{
				if(session != null)
				{
					try {
						session.rollback();
												
					} catch (SQLException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					} catch (Exception e2) {
						// TODO: handle exception
						e2.printStackTrace();
					}
					
				}
				e.printStackTrace();
			}
			finally {
				if(session != null)
				{
					try {
						session.close();
					} catch (DocumentException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			
			try {
				Thread.sleep(interval);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
